/*
 * Copyright (C) 2011 Reeny
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.tobidodo.spritmonclient.service;

import static de.tobidodo.spritmonclient.util.LogHelper.logError;
import static de.tobidodo.spritmonclient.util.LogHelper.logErrorVerbose;
import static de.tobidodo.spritmonclient.util.LogHelper.logInfo;

import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;

import android.os.AsyncTask;
import android.util.Log;
import de.tobidodo.spritmonclient.R;
import de.tobidodo.spritmonclient.data.UrlConstants;
import de.tobidodo.spritmonclient.data.UserLanguage;
import de.tobidodo.spritmonclient.exception.CarIdNotFoundException;
import de.tobidodo.spritmonclient.util.RequestCallback;
import de.tobidodo.spritmonclient.util.RequestParamProvider;
import de.tobidodo.spritmonclient.util.RequestUtil;

/**
 * A service which sends a new fuel data entry to
 * spritmonitor.de. It starts a own thread which calls
 * an other service that fills and sends the actual
 * request.
 * @author user
 *
 */
public class SendNewFuelDataService extends AsyncTask<RequestParamProvider, Integer, Integer> {
	
	public static final String ATTR_TOT_DIST = "attr-tot-dist";
	public static final String ATTR_LAST_DIST = "attr-last-dist";
	public static final String ATTR_FUEL_AMNT = "attr-fuel-amnt";
	public static final String ATTR_FUEL_COST = "attr-fuel-cost";
	public static final String ATTR_FUEL_TYPE = "attr-fuel-type";
	public static final String ATTR_REQU_TKN = "attr-requ-token";
	public static final String ATTR_DB_ID = "attr-db-id";
	
	
	//private long fuelDatabaseId;
	/*
	private String totalDistanceValue;
	private String periodDistanceValue;
	private String fuelAmountValue;
	private String fuelPriceValue;
	private String fuelType;
	private String[] requestUrlTokens;
	*/
	private final RequestCallback requestCallback;
	private Throwable lastError;
	
	public SendNewFuelDataService(RequestCallback callback){
		this.requestCallback = callback;
	}
	
	
	@Override
	protected void onPreExecute() {
		requestCallback.onHttpTransmissionStarted();
	}
	
	@Override
	protected void onCancelled() {
		super.onCancelled();
	}
	
	
	@Override
	protected void onProgressUpdate(Integer... values) {
		requestCallback.onProgressStateChanged(values[0]);
	}
	
	@Override
	protected void onPostExecute(Integer result) {
		requestCallback.postHttpSend(result, lastError);
	}
	
	/**
	 * Send a new HTTP post request to spritmonitor.de which
	 * creates a new fuel data entry on this web page.<p>
	 * The values for the new entry are given by the 
	 * <code>paramProvider</code>.
	 */
	@Override
	protected Integer doInBackground(RequestParamProvider... paramProvider) {
		RequestParamProvider params = paramProvider[0];
		/*
		this.totalDistanceValue =params.getTotalDistanceValue();
		this.periodDistanceValue = params.getPeriodDistanceFormValue();
		this.fuelAmountValue = params.getFuelAmountFormValue();
		this.fuelPriceValue = params.getFuelPriceFormValue();
		this.fuelType = params.getFuelType();
		this.requestUrlTokens = params.getRequestUrlTokens();
		*/
		//this.fuelDatabaseId = intent.getLongExtra(ATTR_DB_ID, -1);
		
		try{
	    	return startDataTransmission(params);
	    	
    	}
    	catch(Exception exc){
    		lastError = exc;
    	}
		
		return -1;
	}
	

	
	

	/**
	 * First this method creates a new http client which is
	 * used for the communication with spritmonitor.de. Then
	 * it trys to login to spritmonitor.de with the account
	 * data given by the preferences. If the login is
	 * successfull the data saved in this instance is
	 * transmitted by an other service. At the end the data
	 * entry is marked as "transmitted".<p>
	 * The return value shows the result state. A "0" means
	 * that the request is send with success.
	 * @return  The result state of this request.
	 */
	private int startDataTransmission(RequestParamProvider params) {
		// Create a new HttpClient
		DefaultHttpClient httpclient = new DefaultHttpClient();

		LoginResult loginResult = login(httpclient, params);
		if (!loginResult.isSuccess()){
			return loginResult.errorMessageId();
		}
	
		boolean success = RequestUtil.submitFuelData(requestCallback, params, loginResult.carId(), loginResult.userLanguage(), httpclient);
		if (success){
			Log.i("main", "Data send sucessful!");
		}
		
		// TODO: return other value on no success
		return 0;	
	}

	
	/**
	 * Try to login to spritmonitor.de with the account data
	 * which are given by the user preferences. The given 
	 * http client is used for the communication.
	 * @param httpclient The client used for the login.
	 * @return A LoginResult object with either the error message id or
	 * 		with a String containing the found car id of the user.
	 */
	public LoginResult login(final DefaultHttpClient httpclient, RequestParamProvider loginData){
		Log.d("main", "start login to spritmonitor.de...");

		
		String username = loginData.getLoginUserName();
		String userpwd = loginData.getLoginUserPassword();

		logInfo("user="+username+", passwort="+userpwd);
		if (username == null || username.length() == 0 || userpwd == null || userpwd.length() == 0){
			Log.e("main","No username or password set for accessing spritmonitor.de.");
			return LoginResult.createFailureResult(R.string.missing_login_data);
		}


		// creates the URI for the login to spritmonitor
		URI loginuri;
		try {
			loginuri = getLoginUri();
		} catch (MalformedURLException e) {
			logErrorVerbose("Error: "+e, e);
			return LoginResult.createFailureResult(R.string.invalid_url);
		} catch (URISyntaxException e) {
			logErrorVerbose("Error: "+e, e);
			return LoginResult.createFailureResult(R.string.invalid_url);
		}


		// get the login form
		
		HttpGet httpget = new HttpGet(loginuri);

		HttpContext httpCtx = new BasicHttpContext();
		HttpResponse result = null;
		try {
			result = httpclient.execute(httpget, httpCtx);
		} catch (ClientProtocolException e) {
			logErrorVerbose("Error: "+e, e);
			return LoginResult.createFailureResult(R.string.spritmon_connect_error);
		} catch (IOException e) {
			logErrorVerbose("Error: "+e, e);
			return LoginResult.createFailureResult(R.string.spritmon_connect_error);
		}

		// interprete the result code
		int resultcode = result.getStatusLine().getStatusCode();
		Log.i("main","Resultcode: "+resultcode);
		if (resultcode < 200 || resultcode > 204){
			Log.e(this.getClass().getSimpleName(), "Error on loading login page. HTTP-State: "+resultcode);
			return LoginResult.createFailureResult(R.string.spritmon_connect_error);
		}
		

		// fill the login form and submit the form
		HttpPost httppost = new HttpPost(loginuri);
		HttpResponse response = null;
		try {
			// Add your data
			List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
			nameValuePairs.add(new BasicNameValuePair("username", username));
			nameValuePairs.add(new BasicNameValuePair("password", userpwd));
			httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

			// Execute HTTP Post Request
			response = httpclient.execute(httppost, httpCtx);

		} catch (ClientProtocolException e) {
			logErrorVerbose("Error: "+e, e);
			return LoginResult.createFailureResult(R.string.login_failed);
		} catch (IOException e) {
			logErrorVerbose("Error: "+e, e);
			return LoginResult.createFailureResult(R.string.login_failed);
		}

		// interprete the result code
		resultcode = response.getStatusLine().getStatusCode();
		Log.i("main","Resultcode: "+resultcode);
		if (resultcode < 200 || resultcode > 204){
			logError("Error while login with login form. HTTP-State: "+resultcode);
			return LoginResult.createFailureResult(R.string.login_failed);
		}
		


		// check result HTTP structure
		HttpHost currentHost = (HttpHost) httpCtx.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
		HttpUriRequest currentReq = (HttpUriRequest) httpCtx.getAttribute(ExecutionContext.HTTP_REQUEST);
		String currentUrl = currentHost.toURI() + currentReq.getURI();
		
		// read language from HTTP URL
		UserLanguage lang = null;
	
		if (currentUrl.contains("de/mein_account.html")){
			lang = UserLanguage.DE;
		}
		else if (currentUrl.contains("fr/mon_compte.html")){
			lang = UserLanguage.FR;
		}
		else if (currentUrl.contains("en/my_account.html")){
			lang = UserLanguage.EN;
		}
		else{
			logError("Error while login to Spritmonitor. The response URL after login is not the expected overview, but: "+currentUrl);
			return LoginResult.createFailureResult(R.string.find_language_failed);
		}
		

		
		
		
		
		// read car id from HTTP page
		String carId = null;
		try{
			carId = RequestUtil.determineCarIdFromContent(response);
		}
		catch(CarIdNotFoundException ciex){
			return onCarIdNotFound(ciex);
		}
		
		Long idAsLong = null;
		if (carId != null){
			try{
				idAsLong = Long.parseLong(carId);
			}
			catch(NumberFormatException nfex){
				logErrorVerbose("found car id '"+carId+"' is not a valid number", nfex);
				return LoginResult.createFailureResult(R.string.find_car_id_failed);
			}
		}

		
		
		return LoginResult.createSuccessResult(idAsLong, lang);
		
	}


	private LoginResult onCarIdNotFound(CarIdNotFoundException ciex) {
		if (ciex.getMessage() == null && ciex.getCause() != null){
			logInfo("Error while determining the car id from page content. "+ciex.getCause());
		}
		else{
			logInfo("Can not find car id in page content. "+ciex);
		}
		return LoginResult.createFailureResult(R.string.find_car_id_failed);
	}


	
	
	
	/**
	 * Create the URI for the http login to spritmonitor.de.
	 * @return The generated URI.
	 * @throws MalformedURLException If the combination of all
	 * 		arguments do not represent a valid URL or the
	 * 		protocol is invalid.
	 * @throws URISyntaxException If the generated URL cannot
	 * 		be converted into a URI.
	 */
	private URI getLoginUri() throws MalformedURLException, URISyntaxException{
		UrlConstants loginUrl = UrlConstants.LOGIN_URL;
		
		URL loginurl = new URL(loginUrl.protocol(), loginUrl.host(), loginUrl.subpath());
		Log.d(this.getClass().getSimpleName(), "login-url="+String.valueOf(loginurl.toExternalForm()));

		return loginurl.toURI();

	}
	
	
	static class LoginResult implements Serializable{
		private static final long serialVersionUID = -1413026416442316908L;
		
		private final int errorMessage;
		private final Long carId;
		private final UserLanguage lang;
		
		private LoginResult(int errorMsgId, Long foundid, UserLanguage language){
			this.errorMessage = errorMsgId;
			this.carId = foundid;
			this.lang = language;
		}
		
		public static LoginResult createSuccessResult(Long foundid, UserLanguage language){
			return new LoginResult(-1, foundid, language);
		}

		public static LoginResult createFailureResult(int errorMsgId){
			return new LoginResult(errorMsgId, null, null);
		}
		
		public boolean isSuccess(){
			return carId != null;
		}

		public int errorMessageId() {
			return errorMessage;
		}

		public Long carId() {
			return carId;
		}

		public UserLanguage userLanguage() {
			return lang;
		}
		
		
		
	}
}
